package chrome

import (
	"encoding/json"
	"errors"
	"fmt"
	cmdutils "github.com/MzoroHaHa/dev-tools/backend/app/cmd-utils"
	"github.com/MzoroHaHa/dev-tools/backend/app/logger"
	"github.com/MzoroHaHa/dev-tools/backend/app/server"
	"github.com/MzoroHaHa/dev-tools/backend/app/utils"
	"golang.org/x/net/websocket"
	"io"
	"log/slog"
	"math/rand"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strconv"
	"time"
)

var chromeLogger = logger.NewLogger("chrome")

type Browser struct {
	activeTarget *Target   // 当前显示的页面
	targets      []*Target // 打开的页面
	debugPort    uint      // debug 端口
	chromeArgs   []string  // 启动参数
	chromePath   string    // chrome 程序路径
	cmd          *exec.Cmd //操作系统命令
	logger       *slog.Logger
	browserInfo  *BrowserInfo
}
type BrowserInfo struct {
	Browser              string `json:"Browser"`
	ProtocolVersion      string `json:"Protocol-Version"`
	UserAgent            string `json:"User-Agent"`
	V8Version            string `json:"V8-Version"`
	WebKitVersion        string `json:"WebKit-Version"`
	WebSocketDebuggerUrl string `json:"webSocketDebuggerUrl"`
}

func (bi BrowserInfo) String() string {
	return fmt.Sprintf("BrowserInfo[Browser:%s, ProtocolVersion:%s, UserAgent:%s，WebSocketDebuggerUrl:%s]",
		bi.Browser, bi.ProtocolVersion, bi.UserAgent, bi.WebKitVersion)
}

func (c *Browser) Close() {
	if len(c.targets) > 0 {
		for _, t := range c.targets {
			t.Close()
		}
	}
	if c.cmd != nil {
		_ = c.cmd.Process.Kill()
	}
	ws, err := websocket.Dial(c.browserInfo.WebSocketDebuggerUrl, "", "http://localhost")

	if err == nil {
		defer func(ws *websocket.Conn) {
			err := ws.Close()
			if err != nil {
				c.logger.Error("WebSocketDebuggerUrl close error:", err)
			}
		}(ws)
	}
	if ws != nil {
		_, _ = ws.Write([]byte("{\"method\": \"Browser.close\",\"id\": " + strconv.Itoa(closePageId) + "}"))
	}
	c.cmd = nil
	c.logger = nil
	c.targets = nil
	c.activeTarget = nil
	c.browserInfo = nil
	c.chromePath = ""
}

func (c *Browser) GetArgs() []string {
	return c.chromeArgs
}

// ActiveTargetById 激活一个Target
func (c *Browser) ActiveTargetById(targetId string) error {
	if c.activeTarget != nil && c.activeTarget.targetInfo != nil &&
		targetId == c.activeTarget.targetInfo.Id {
		return nil
	}
	var target *Target
	for _, t := range c.targets {
		if t.targetInfo.Id == targetId {
			target = t
			break
		}
	}
	c.activeTarget = target
	// 连接
	c.logger.Info("ActiveTargetById:", targetId)
	err := c.activeTarget.initTarget()
	return err
}

// GetTargetInfos 获得Target 信息
func (c *Browser) GetTargetInfos() []TargetInfo {
	targetInfos := make([]TargetInfo, 0)
	for _, t := range c.targets {
		targetInfos = append(targetInfos, CloneTargetInfo(t.targetInfo))
	}
	return targetInfos
}

// GetActiveTargetInfo 获得激活的 Target 信息
func (c *Browser) GetActiveTargetInfo() *TargetInfo {
	if c.activeTarget == nil {
		return nil
	}
	info := CloneTargetInfo(c.activeTarget.targetInfo)
	return &info
}

func (c *Browser) GetActiveTargetUrl() string {
	// refresh TargetInfos
	err := c.getPageList()
	if err != nil {
		c.logger.Error("GetActiveTargetUrl:", err)
		return ""
	}
	if c.activeTarget == nil {
		return ""
	}
	return c.activeTarget.targetInfo.Url
}

func (c *Browser) getBrowserInfo() (BrowserInfo, error) {

	if c.browserInfo != nil {
		return BrowserInfo{
			Browser:              c.browserInfo.Browser,
			ProtocolVersion:      c.browserInfo.ProtocolVersion,
			UserAgent:            c.browserInfo.UserAgent,
			WebKitVersion:        c.browserInfo.WebKitVersion,
			WebSocketDebuggerUrl: c.browserInfo.WebSocketDebuggerUrl,
			V8Version:            c.browserInfo.V8Version,
		}, nil
	}

	resp, err := http.Get(fmt.Sprintf("%s:%d%s", "http://localhost", c.debugPort, "/json/version"))
	if resp != nil {
		defer func(Body io.ReadCloser) {
			err := Body.Close()
			if err != nil {
				panic(err)
			}
		}(resp.Body)
	}
	info := BrowserInfo{}
	if err == nil && resp != nil && resp.StatusCode == 200 {
		bodyBytes, _ := io.ReadAll(resp.Body)
		c.logger.Debug("browser info:" + string(bodyBytes))
		err = json.Unmarshal(bodyBytes, &info)
	} else {
		resErr := ""
		if resp == nil {
			resErr = "response is nil"
		} else {
			resErr = fmt.Sprintf("response status code: %d", resp.StatusCode)
		}
		c.logger.Error("get browser info error:", "error", err, "status", resErr)
		return info, err
	}
	c.browserInfo = &info

	return BrowserInfo{
		Browser:              c.browserInfo.Browser,
		ProtocolVersion:      c.browserInfo.ProtocolVersion,
		UserAgent:            c.browserInfo.UserAgent,
		WebKitVersion:        c.browserInfo.WebKitVersion,
		WebSocketDebuggerUrl: c.browserInfo.WebSocketDebuggerUrl,
		V8Version:            c.browserInfo.V8Version,
	}, err

}

func (c *Browser) initChrome() error {
	c.logger = logger.NewLogger("browser")
	c.logger.With("webSocketDebuggerUrl", "init")
	c.logger.Info("initChrome")
	_, err := c.getBrowserInfo()
	if err != nil {
		ticker := time.NewTicker(time.Millisecond * 200)
		defer ticker.Stop()
		for i := 0; i < 5; i++ {
			<-ticker.C
			_, err = c.getBrowserInfo()
			if err == nil && len(c.browserInfo.UserAgent) > 0 {
				break
			}
			c.logger.Error("initChrome", "err", err)
		}
	}
	if c.browserInfo == nil || len(c.browserInfo.UserAgent) == 0 {
		return errors.New("can't find WebSocketDebuggerUrl")
	}
	c.logger = c.logger.With("webSocketDebuggerUrl", c.browserInfo.WebSocketDebuggerUrl)

	err = c.getPageList()
	if err != nil {
		return err
	}
	c.logger.Info("browser init success")
	targets := c.GetTargetInfos()
	if len(targets) == 0 {
		return nil
	}
	err = c.ActiveTargetById(targets[0].Id)
	if err != nil {
		return err
	}
	return nil
}

// getPageList 更新 Browser 的pages 数据
func (c *Browser) getPageList() error {
	pageList := make([]*TargetInfo, 0)
	url := fmt.Sprintf("%s:%d%s", "http://localhost", c.debugPort, "/json/list")
	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	defer func(Body io.ReadCloser) {
		err := Body.Close()
		if err != nil {
			chromeLogger.Error("close response /json/list body error", "error", err)
		}
	}(resp.Body)
	if resp.StatusCode != 200 {
		return errors.New("/json/list error: statusCode is " + resp.Status)
	}
	bodyBytes, _ := io.ReadAll(resp.Body)
	err = json.Unmarshal(bodyBytes, &pageList)
	if err != nil {
		return err
	}

	if len(pageList) == 0 {
		c.targets = make([]*Target, 0)
	}
	targets := make([]*Target, 0)
	for _, page := range pageList {
		targets = append(targets, &Target{
			targetInfo: page,
		})
	}
	c.targets = targets
	return nil
}

func NewBrowser() (*Browser, error) {
	chromePath := cmdutils.GetChromePath()
	if len(chromePath) == 0 {
		chromePath = findChrome()
	}

	debuggingPort, e := utils.FindPort(uint(10000), uint(20000))
	if e != nil {
		debuggingPort = 0
		return nil, e
	}

	var chromeArgs []string

	_cArgs := cmdutils.GetChromeArgs()
	if len(_cArgs) > 0 {
		chromeArgs = _cArgs
		chromeArgs = append(chromeArgs,
			"--user-data-dir="+getUserDir(),
			"--remote-debugging-port="+strconv.Itoa(int(debuggingPort)))
	} else {
		chromeArgs = []string{
			"--allow-pre-commit-input",
			"--disable-background-networking",
			"--disable-background-timer-throttling",
			"--disable-backgrounding-occluded-windows",
			"--disable-breakpad",
			"--remote-allow-origins=*",
			"--disable-client-side-phishing-detection",
			"--disable-component-extensions-with-background-pages",
			"--disable-default-apps",
			"--disable-dev-shm-usage",
			"--disable-extensions",
			"--disable-features=Translate,BackForwardCache,AcceptCHFrame,AvoidUnnecessaryBeforeUnloadCheckSync",
			"--disable-hang-monitor",
			"--disable-ipc-flooding-protection",
			"--disable-popup-blocking",
			"--disable-prompt-on-repost",
			"--disable-renderer-backgrounding",
			"--disable-sync",
			"--enable-automation",
			"--force-color-profile=srgb",
			"--metrics-recording-only",
			"--no-first-run",
			"--password-store=basic",
			"--use-mock-keychain",
			"--enable-blink-features=IdleDetection",
			"--export-tagged-pdf",
			"--no-sandbox",
			//"--start-maximized",
			//"--window-size=1366,768",
			"--user-data-dir=" + getUserDir(),
			"--remote-debugging-port=" + strconv.Itoa(int(debuggingPort)),
		}
	}

	chromeArgs = append(chromeArgs, "--app="+pageUrl())

	err := runChrome(chromePath, chromeArgs...)
	if err != nil {
		return nil, err
	}

	browser := &Browser{
		debugPort:  debuggingPort,
		chromeArgs: chromeArgs,
		chromePath: chromePath,
	}
	err = browser.initChrome()

	if err != nil {
		chromeLogger.Error("init chrome error", "error", err)
		return nil, err
	}
	chromeLogger.Info("chrome init success")
	return browser, nil
}

func findChrome() string {
	_os := runtime.GOOS
	switch _os {
	case "windows":
		chromePath := []string{"C:/Program Files/Google/Chrome/Application/chrome.exe", "C:/Program Files (x86)/Google/Chrome/Application/chrome.exe"}
		for _, pt := range chromePath {
			if _, err := os.Stat(pt); err == nil {
				return pt
			}
		}
	case "linux":
		chromePath := []string{"/usr/bin/google-chrome-stable", "/usr/bin/google-chrome"}
		for _, pt := range chromePath {
			if _, err := os.Stat(pt); err == nil {
				return pt
			}
		}
	default:
		return ""
	}
	return ""
}

// getUserDir user-data-dir 参数, 默认是{程序所在dir}/chrome
func getUserDir() string {
	dir := cmdutils.GetUserDataDir()
	if len(dir) == 0 {
		dir = filepath.Join(cmdutils.GetProgramDir(), "tmp", "chrome_"+strconv.Itoa(rand.Int()))
	}
	return dir
}

func pageUrl() (url string) {
	url = cmdutils.GetPageUrl()
	if len(url) == 0 {
		url = server.UsingServer.GetStaticUrl()
	}
	return
}
